<template>
  <view class="lime-echart" :style="customStyle" v-if="canvasId" ref="limeEchart" :aria-label="ariaLabel">
    <!-- #ifndef APP-NVUE -->
    <canvas
      class="lime-echart__canvas"
      v-if="use2dCanvas"
      type="2d"
      :id="canvasId"
      :style="canvasStyle"
      :disable-scroll="isDisableScroll"
      @touchstart="touchStart"
      @touchmove="touchMove"
      @touchend="touchEnd"
    />
    <canvas
      class="lime-echart__canvas"
      v-else-if="isPc"
      :style="canvasStyle"
      :id="canvasId"
      :canvas-id="canvasId"
      :disable-scroll="isDisableScroll"
      @mousedown="touchStart"
      @mousemove="touchMove"
      @mouseup="touchEnd"
    />
    <canvas
      class="lime-echart__canvas"
      v-else
      :width="nodeWidth"
      :height="nodeHeight"
      :style="canvasStyle"
      :canvas-id="canvasId"
      :id="canvasId"
      :disable-scroll="isDisableScroll"
      @touchstart="touchStart"
      @touchmove="touchMove"
      @touchend="touchEnd"
    />
    <canvas v-if="isOffscreenCanvas" :style="offscreenStyle" :canvas-id="offscreenCanvasId"></canvas>
    <!-- #endif -->
    <!-- #ifdef APP-NVUE -->
    <web-view
      class="lime-echart__canvas"
      :id="canvasId"
      :style="canvasStyle"
      :webview-styles="webviewStyles"
      ref="webview"
      src="/uni_modules/lime-echart/static/index.html"
      @pagefinish="finished = true"
      @onPostMessage="onMessage"
    ></web-view>
    <!-- #endif -->
  </view>
</template>

<script>
// #ifdef VUE3
// #ifdef APP-PLUS
global = {}
// #endif
// #endif
// #ifndef APP-NVUE
import { Canvas, setCanvasCreator, dispatch } from './canvas'
import { wrapTouch, convertTouchesToArray, devicePixelRatio, sleep, canIUseCanvas2d, getRect } from './utils'
// #endif
// #ifdef APP-NVUE
import { base64ToPath, sleep } from './utils'
import { Echarts } from './nvue'
// #endif
const charts = {}
const echartsObj = {}
export default {
  name: 'lime-echart',
  props: {
    // #ifdef MP-WEIXIN || MP-TOUTIAO
    type: {
      type: String,
      default: '2d',
    },
    // #endif
    // #ifdef APP-NVUE
    webviewStyles: Object,
    // hybrid: Boolean,
    // #endif
    customStyle: String,
    isDisableScroll: Boolean,
    isClickable: {
      type: Boolean,
      default: true,
    },
    enableHover: Boolean,
    beforeDelay: {
      type: Number,
      default: 30,
    },
  },
  data() {
    return {
      // #ifdef MP-WEIXIN || MP-TOUTIAO || MP-ALIPAY
      use2dCanvas: true,
      // #endif
      // #ifndef MP-WEIXIN || MP-TOUTIAO || MP-ALIPAY
      use2dCanvas: false,
      // #endif
      ariaLabel: '图表',
      width: null,
      height: null,
      nodeWidth: null,
      nodeHeight: null,
      // canvasNode: null,
      config: {},
      inited: false,
      finished: false,
      file: '',
      platform: '',
      isPc: false,
      isDown: false,
      isOffscreenCanvas: false,
      offscreenWidth: 0,
      offscreenHeight: 0,
    }
  },
  computed: {
    canvasId() {
      return `lime-echart${(this._ && this._.uid) || this._uid}`
    },
    offscreenCanvasId() {
      return `${this.canvasId}_offscreen`
    },
    offscreenStyle() {
      return `width:${this.offscreenWidth}px;height: ${this.offscreenHeight}px; position: fixed; left: 99999px; background: red`
    },
    canvasStyle() {
      return this.width && this.height ? 'width:' + this.width + 'px;height:' + this.height + 'px' : ''
    },
  },
  // #ifndef VUE3
  beforeDestroy() {
    this.clear()
    this.dispose()
    // #ifdef H5
    if (this.isPc) {
      document.removeEventListener('mousewheel', this.mousewheel)
    }
    // #endif
  },
  // #endif
  // #ifdef VUE3
  unmounted() {
    this.clear()
    this.dispose()
    // #ifdef H5
    if (this.isPc) {
      document.removeEventListener('mousewheel', this.mousewheel)
    }
    // #endif
  },
  // #endif
  created() {
    // #ifdef H5
    if (!('ontouchstart' in window)) {
      this.isPc = true
      document.addEventListener('mousewheel', this.mousewheel)
    }
    // #endif
    // #ifdef MP-WEIXIN || MP-TOUTIAO || MP-ALIPAY
    const { platform } = uni.getSystemInfoSync()
    this.isPC = /windows/i.test(platform)
    // #endif
    this.use2dCanvas = this.type === '2d' && canIUseCanvas2d()
  },
  mounted() {
    this.$nextTick(() => {
      this.$emit('finished')
    })
  },
  methods: {
    // #ifdef APP-NVUE
    onMessage(e) {
      const res = e?.detail?.data[0] || null
      if (res?.event) {
        if (res.event === 'inited') {
          this.inited = true
        }
        this.$emit(res.event, JSON.parse(res.data))
      } else if (res?.file) {
        this.file = res.data
      } else if (!res[0] && JSON.stringify(res[0]) != '{}') {
        console.error(res)
      } else {
        console.log(...res)
      }
    },
    // #endif
    setChart(callback) {
      if (!this.chart) {
        console.warn(`组件还未初始化，请先使用 init`)
        return
      }
      if (typeof callback === 'function' && this.chart) {
        callback(this.chart)
      }
      // #ifdef APP-NVUE
      if (typeof callback === 'function') {
        this.$refs.webview.evalJs(
          `setChart(${JSON.stringify(callback.toString())}, ${JSON.stringify(this.chart.options)})`
        )
      }
      // #endif
    },
    setOption() {
      if (!this.chart || !this.chart.setOption) {
        console.warn(`组件还未初始化，请先使用 init`)
        return
      }
      this.chart.setOption(...arguments)
    },
    showLoading() {
      if (this.chart) {
        this.chart.showLoading(...arguments)
      }
    },
    hideLoading() {
      if (this.chart) {
        this.chart.hideLoading()
      }
    },
    clear() {
      if (this.chart) {
        this.chart.clear()
      }
    },
    dispose() {
      if (this.chart) {
        this.chart.dispose()
      }
    },
    resize(size) {
      if (size && size.width && size.height) {
        this.height = size.height
        this.width = size.width
        if (this.chart) {
          this.chart.resize(size)
        }
      } else {
        this.$nextTick(() => {
          uni
            .createSelectorQuery()
            .in(this)
            .select(`.lime-echart`)
            .boundingClientRect()
            .exec((res) => {
              if (res) {
                let { width, height } = res[0]
                this.width = width = width || 300
                this.height = height = height || 300
                this.chart.resize({ width, height })
              }
            })
        })
      }
    },
    canvasToTempFilePath(args = {}) {
      // #ifndef APP-NVUE
      const { use2dCanvas, canvasId } = this
      return new Promise((resolve, reject) => {
        const copyArgs = Object.assign(
          {
            canvasId,
            success: resolve,
            fail: reject,
          },
          args
        )
        if (use2dCanvas) {
          delete copyArgs.canvasId
          copyArgs.canvas = this.canvasNode
        }
        uni.canvasToTempFilePath(copyArgs, this)
      })
      // #endif
      // #ifdef APP-NVUE
      this.file = ''
      this.$refs.webview.evalJs(`canvasToTempFilePath()`)
      return new Promise((resolve, reject) => {
        this.$watch('file', async (file) => {
          if (file) {
            const tempFilePath = await base64ToPath(file)
            resolve(args.success({ tempFilePath }))
          } else {
            reject(args.fail({ error: `` }))
          }
        })
      })
      // #endif
    },
    async init(echarts, ...args) {
      // #ifndef APP-NVUE
      if (arguments && arguments.length < 1) {
        console.error('缺少参数：init(echarts, theme?:string, opts?: object, callback?: function)')
        return
      }
      // #endif
      let theme = null,
        opts = {},
        callback

      Array.from(arguments).forEach((item) => {
        if (typeof item === 'function') {
          callback = item
        }
        if (['string'].includes(typeof item)) {
          theme = item
        }
        if (typeof item === 'object') {
          opts = item
        }
      })

      if (this.beforeDelay) {
        await sleep(this.beforeDelay)
      }
      let config = await this.getContext()
      // #ifndef APP-NVUE
      setCanvasCreator(echarts, config)
      this.chart = echarts.init(config.canvas, theme, Object.assign({}, config, opts))
      if (typeof callback === 'function') {
        callback(this.chart)
      } else {
        return this.chart
      }
      // #endif
      // #ifdef APP-NVUE
      this.chart = new Echarts(this.$refs.webview)
      this.$refs.webview.evalJs(`init(null, null, ${JSON.stringify(opts)}, ${theme})`)
      if (callback) {
        callback(this.chart)
      } else {
        return this.chart
      }
      // #endif
    },
    getContext() {
      // #ifdef APP-NVUE
      if (this.finished) {
        return Promise.resolve(this.finished)
      }
      return new Promise((resolve) => {
        this.$watch('finished', (val) => {
          if (val) {
            resolve(this.finished)
          }
        })
      })
      // #endif
      // #ifndef APP-NVUE
      return getRect(`#${this.canvasId}`, {
        context: this,
        type: this.use2dCanvas ? 'fields' : 'boundingClientRect',
      }).then((res) => {
        if (res) {
          let dpr = devicePixelRatio
          let { width, height, node } = res
          let canvas
          this.width = width = width || 300
          this.height = height = height || 300
          if (node) {
            const ctx = node.getContext('2d')
            canvas = new Canvas(ctx, this, true, node)
            this.canvasNode = node
          } else {
            // #ifdef MP-TOUTIAO
            dpr = !this.isPC ? devicePixelRatio : 1 // 1.25
            // #endif
            // #ifndef MP-ALIPAY || MP-TOUTIAO
            dpr = this.isPC ? devicePixelRatio : 1
            // #endif
            // #ifdef MP-ALIPAY || MP-LARK
            dpr = devicePixelRatio
            // #endif
            this.rect = res
            this.nodeWidth = width * dpr
            this.nodeHeight = height * dpr
            const ctx = uni.createCanvasContext(this.canvasId, this)
            canvas = new Canvas(ctx, this, false)
          }
          return { canvas, width, height, devicePixelRatio: dpr, node }
        } else {
          return {}
        }
      })
      // #endif
    },
    // #ifndef APP-NVUE
    getRelative(e) {
      return { x: e.pageX - this.rect.left, y: e.pageY - this.rect.top, wheelDelta: e.wheelDelta }
    },
    getTouch(e, touches) {
      const { x } = (touches && touches[0]) || {}
      return x ? touches[0] : this.getRelative(e)
    },
    touchStart(e) {
      this.isDown = true
      const touches = convertTouchesToArray(e.touches)
      if (this.chart && ((touches.length > 0 && e.type != 'mousemove') || e.type == 'mousedown')) {
        const touch = this.getTouch(e, touches)
        this.startX = touch.x
        this.startY = touch.y
        this.startT = new Date()
        const handler = this.chart.getZr().handler
        dispatch.call(handler, 'mousedown', touch)
        dispatch.call(handler, 'mousemove', touch)
        handler.processGesture(wrapTouch(e), 'start')
        clearTimeout(this.endTimer)
      }
    },
    touchMove(e) {
      if (this.isPc && this.enableHover && !this.isDown) {
        this.isDown = true
      }
      const touches = convertTouchesToArray(e.touches)
      if (this.chart && ((touches.length > 0 && e.type != 'mousemove') || (e.type == 'mousemove' && this.isDown))) {
        const handler = this.chart.getZr().handler
        dispatch.call(handler, 'mousemove', this.getTouch(e, touches))
        handler.processGesture(wrapTouch(e), 'change')
      }
    },
    touchEnd(e) {
      this.isDown = false
      if (this.chart) {
        const touches = convertTouchesToArray(e.changedTouches)
        const { x } = (touches && touches[0]) || {}
        const touch = (x ? touches[0] : this.getRelative(e)) || {}
        const handler = this.chart.getZr().handler
        const isClick = Math.abs(touch.x - this.startX) < 10 && new Date() - this.startT < 200
        dispatch.call(handler, 'mouseup', touch)
        handler.processGesture(wrapTouch(e), 'end')
        if (isClick) {
          dispatch.call(handler, 'click', touch)
        } else {
          this.endTimer = setTimeout(() => {
            dispatch.call(handler, 'mousemove', { x: 999999999, y: 999999999 })
            dispatch.call(handler, 'mouseup', { x: 999999999, y: 999999999 })
          }, 50)
        }
      }
    },
    // #endif
    // #ifdef H5
    mousewheel(e) {
      if (this.chart) {
        dispatch.call(this.chart.getZr().handler, 'mousewheel', this.getTouch(e))
      }
    },
    // #endif
  },
}
</script>
<style>
.lime-echart {
  position: relative;
  /* #ifndef APP-NVUE */
  width: 100%;
  height: 100%;
  /* #endif */
  /* #ifdef APP-NVUE */
  flex: 1;
  /* #endif */
}

.lime-echart__canvas {
  /* #ifndef APP-NVUE */
  width: 100%;
  height: 100%;
  /* #endif */
  /* #ifdef APP-NVUE */
  flex: 1;
  /* #endif */
}
</style>
